home *** CD-ROM | disk | FTP | other *** search
Wrap
/** * The main namespace class for the FireFound service. * * @author chris */ var FIREFOUND = { /** * Whether debug mode is turned on. Used mostly to control logging. */ debug : true, /** * Whether this instance of the service is active. * This whole thing should really be replaced with a component that runs in the background, * but I've run into some issues with that because of the broken geolocation support in Firefox. * @see https://bugzilla.mozilla.org/show_bug.cgi?id=493615 */ active : false, /** * Reference for this object's geolocation listener entry. */ watchId : null, /** * Flag to disable the pref observer in various instances. */ loadingSettings : false, /** * Hold the user's password in memory so that they're not prompted for the master password * multiple times per browsing session if they're using it. */ password : null, /** * Holds the preferences service after initialization. */ prefs : null, /** * A timer we use to delay sending preferences to the server, since the observer is triggered * after every keystroke in Fennec. */ prefObserverTimeout : null, /** * Stringbundle */ strings : null, /** * Returns the current FireFound username. */ get account() { return this.prefs.getCharPref("username"); }, /** * Service initializer. * * @param boolean bareBones Whether to load just the bare minimum to use this object. */ load : function (bareBones) { this.prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("extensions.firefound."); this.prefs.QueryInterface(Components.interfaces.nsIPrefBranch2); this.prefs.addObserver("", FIREFOUND, false); if (document.getElementById("firefound-bundle")) { this.strings = document.getElementById("firefound-bundle"); } /** * A bare-bones initialization would be for something like options dialog in Firefox */ if (!bareBones) { // Check if there's already a window running with the FireFound service. var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator); var enumerator = wm.getEnumerator(null); while (enumerator.hasMoreElements()) { var win = enumerator.getNext(); if ((win != window) && (win.FIREFOUND && win.FIREFOUND.active)) { return; } } this.active = true; // Register with the geolocation service. this.watchId = GEOLOCATION.watchPosition(function (position) { FIREFOUND.newLocation(position); }, function (error) { FIREFOUND.locationError(error); }); // If there's no account associated with this profile, prompt the user for one. if (!this.account) { setTimeout(function () { FIREFOUND.getAccount(); }, 5000); } else { this.getPreferencesForFennec(); } // Show a first-run page if this is the first run after a new install or upgrade. var version = Components.classes["@mozilla.org/extensions/manager;1"].getService(Components.interfaces.nsIExtensionManager).getItemForID("firefound@efinke.com").version; var oldVersion = this.prefs.getCharPref("version"); if (version != oldVersion) { this.prefs.setCharPref("version", version); setTimeout(function () { if (typeof Browser != 'undefined') { // Fennec Browser.addTab(FIREFOUND.prefs.getCharPref("host") + "/firstrun/", true); } else { // Firefox var browser = getBrowser(); browser.selectedTab = browser.addTab(FIREFOUND.prefs.getCharPref("host") + "/firstrun/"); } }, 3000); } } }, /** * Cleans up loose ends. */ unload : function () { /** * If this is an active instance, check for other windows and active one of them. */ this.prefs.removeObserver("", FIREFOUND); if (this.active) { this.active = false; GEOLOCATION.clearWatch(this.watchId); /** * Pass the torch to any other non-active FireFound-enabled window. */ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator); var enumerator = wm.getEnumerator(null); while (enumerator.hasMoreElements()) { var win = enumerator.getNext(); if (win != window && win.FIREFOUND && !win.FIREFOUND.active) { win.FIREFOUND.load(); return; } } } }, /** * Returns a reference to the active Firefound object, or false if none are found. * * @return boolean */ getActiveFireFound : function () { var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator); var enumerator = wm.getEnumerator(null); while (enumerator.hasMoreElements()) { var win = enumerator.getNext(); if (win.FIREFOUND && win.FIREFOUND.active) { return win.FIREFOUND; } } return false; }, /** * Preference observer, used only for Fennec, but will probably be used for Firefox eventually anyway. * We delay the actual observer code because it fires after every keystroke in Fennec. * * @param string subject * @param string topic The event being observed. * @param string data In our case, the preference that was changed. */ observe : function(subject, topic, data) { clearTimeout(FIREFOUND.prefObserverTimeout); FIREFOUND.prefObserverTimeout = setTimeout(function (subject, topic, data) { FIREFOUND.prefChange(subject, topic, data); }, 1000, subject, topic, data); }, /** * Preference observer, used only for Fennec, but will probably be used for Firefox eventually anyway. * * @param string subject * @param string topic The event being observed. * @param string data In our case, the preference that was changed. */ prefChange : function (subject, topic, data) { if (topic != "nsPref:changed") { return; } if (FIREFOUND.active && !FIREFOUND.loadingSettings) { var preferences = null; switch(data) { case "fennec.emailAddress": var preferences = { "email" : this.prefs.getCharPref("fennec.emailAddress") }; break; case "fennec.miles": var preferences = { "miles" : this.prefs.getIntPref("fennec.miles") }; break; case "fennec.protection.history": case "fennec.protection.downloads": case "fennec.protection.formdata": case "fennec.protection.cache": case "fennec.protection.offlineApps": case "fennec.protection.passwords": case "fennec.protection.cookies": case "fennec.protection.sessions": case "fennec.protection.siteSettings": var preferences = { "data_protection" : { "history" : this.prefs.getBoolPref("fennec.protection.history"), "passwords" : this.prefs.getBoolPref("fennec.protection.passwords"), "downloads" : this.prefs.getBoolPref("fennec.protection.downloads"), "cookies" : this.prefs.getBoolPref("fennec.protection.cookies"), "formdata" : this.prefs.getBoolPref("fennec.protection.formdata"), "sessions" : this.prefs.getBoolPref("fennec.protection.sessions"), "cache" : this.prefs.getBoolPref("fennec.protection.cache"), "siteSettings" : this.prefs.getBoolPref("fennec.protection.siteSettings"), "offlineApps" : this.prefs.getBoolPref("fennec.protection.offlineApps") } }; break; } if (preferences) { /** * Update the setting on the server. */ this.sendPreferences(preferences); } } }, /** * Checks if the environment is Fennec, and if so, retrieves the settings from the server. */ getPreferencesForFennec : function () { var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo); if (appInfo.ID == '{a23983c0-fd0e-11dc-95ff-0800200c9a66}') { // This is Fennec. function callback(rv) { FIREFOUND.loadingSettings = true; if (!rv.email) rv.email = ""; FIREFOUND.prefs.setCharPref("fennec.emailAddress", rv.email); FIREFOUND.prefs.setIntPref("fennec.miles", rv.miles); FIREFOUND.prefs.setBoolPref("fennec.protection.history", rv.data_protection.history); FIREFOUND.prefs.setBoolPref("fennec.protection.passwords", rv.data_protection.passwords); FIREFOUND.prefs.setBoolPref("fennec.protection.downloads", rv.data_protection.downloads); FIREFOUND.prefs.setBoolPref("fennec.protection.cookies", rv.data_protection.cookies); FIREFOUND.prefs.setBoolPref("fennec.protection.formdata", rv.data_protection.formdata); FIREFOUND.prefs.setBoolPref("fennec.protection.sessions", rv.data_protection.sessions); FIREFOUND.prefs.setBoolPref("fennec.protection.cache", rv.data_protection.cache); FIREFOUND.prefs.setBoolPref("fennec.protection.siteSettings", rv.data_protection.siteSettings); FIREFOUND.prefs.setBoolPref("fennec.protection.offlineApps", rv.data_protection.offlineApps); FIREFOUND.loadingSettings = false; } this.getPreferences(callback); } }, /** * Initialization for the settings dialog in Firefox. */ getPreferencesForFirefox : function () { // Code to load the preference data for the full-blown settings window in Firefox. function callback(rv) { document.getElementById("loading").style.visibility = "hidden"; document.getElementById("controls").style.visibility = "visible"; if ("msg" in rv) { if ("code" in rv && rv.code == "ERROR_NO_ACCOUNT") { FIREFOUND.getActiveFireFound().getAccount(); window.close(); } else { alert(rv.msg); } } else { var email = rv.email var miles = rv.miles; var edp = rv.data_protection; document.getElementById("email").value = email; document.getElementById("miles").value = miles; document.getElementById("edp_history").checked = edp.history; document.getElementById("edp_passwords").checked = edp.passwords; document.getElementById("edp_downloads").checked = edp.downloads; document.getElementById("edp_cookies").checked = edp.cookies; document.getElementById("edp_formdata").checked = edp.formdata; document.getElementById("edp_sessions").checked = edp.sessions; document.getElementById("edp_cache").checked = edp.cache; document.getElementById("edp_siteSettings").checked = edp.siteSettings; document.getElementById("edp_offlineApps").checked = edp.offlineApps; } } this.getPreferences(callback); }, /** * Prompt the user to choose a username and password (or supply an existing pair). * * @param function successCallback The callback to call when all is said and done with this function. * @param function errorCallback The error callback. */ getAccount : function (successCallback, errorCallback) { this.password = null; var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] .getService(Components.interfaces.nsIPromptService); username = { value: FIREFOUND.account }; password = { value: "" }; check = { value: false }; okorcancel = prompts.promptUsernameAndPassword(window, this.strings.getString("firefound.registration.title"), this.strings.getString("firefound.registration.description"), username, password, null, check); if (!okorcancel) { if (errorCallback) { errorCallback(); } else { // Don't bug the user for the rest of this window's session. this.unload(); } } else { // Post the auth pair to the FireFound service. var json = { username : username.value, password : password.value }; json = JSON.stringify(json); var req = new XMLHttpRequest(); req.parent = this; req.open("POST", this.prefs.getCharPref("host") + "/api/account.json", true); req.setRequestHeader("Content-Type", "application/json"); req.setRequestHeader("Content-Length", json.length); req.onreadystatechange = function () { if (req.readyState == 4) { if (FIREFOUND.prefs.getBoolPref("debug")) { FIREFOUND.log("getAccount: " + req.responseText); } if (req.status == 200) { // Success: Save this username and password. req.parent.prefs.setCharPref("username", username.value.toLowerCase()); req.parent.setPassword(username.value.toLowerCase(), password.value); // Request the current location as a baseline. if (req.parent.watchId) { GEOLOCATION.getCurrentPosition( function (position) { FIREFOUND.newLocation(position); }, function (error) { FIREFOUND.locationError(error); } ); } if (successCallback) { successCallback(); } /** * In case this is Fennec, download the current settings. */ FIREFOUND.getPreferencesForFennec(); } else { // Could be an invalid username or incorrect password. var rv = JSON.parse(req.responseText); alert(rv.msg); // Try again. req.parent.getAccount(successCallback, errorCallback); } } }; req.send(json); } }, /** * Retrieves user preferences from the server and populates the settings form. * * @param function callback The function that is called with the preferences retrieved from the server. */ getPreferences : function (callback) { var username = this.account; if (!this.account) { this.getAccount(function () { FIREFOUND.getPreferences(); }, function () { window.close(); }); return; } var password = this.getPassword(username); if (!password) { this.getAccount(function () { FIREFOUND.getPreferences(); }, function () { window.close(); }); return; } var json = { username : username, password : password }; json = JSON.stringify(json); var req = new XMLHttpRequest(); req.parent = this; req.open("POST", this.prefs.getCharPref("host") + "/api/preferences.json", true); req.setRequestHeader("Content-Type", "application/json"); req.setRequestHeader("Content-Length", json.length); req.onreadystatechange = function () { if (req.readyState == 4) { if (callback) { var rv = JSON.parse(req.responseText); if (callback) { callback(rv); } } } }; req.send(json); }, /** * Saves user preference changes to the server (called from settings.xul) * * @return boolean Returns false to prevent the dialog from closing before the request completes. */ setPreferences : function () { document.getElementById("loading").selectedIndex = 1; document.getElementById("loading").style.visibility = "visible"; var preferences = { "email" : document.getElementById("email").value, "miles" : document.getElementById("miles").value, "data_protection" : { "history" : document.getElementById("edp_history").checked, "passwords" : document.getElementById("edp_passwords").checked, "downloads" : document.getElementById("edp_downloads").checked, "cookies" :document.getElementById("edp_cookies").checked, "formdata" :document.getElementById("edp_formdata").checked, "sessions" :document.getElementById("edp_sessions").checked, "cache" :document.getElementById("edp_cache").checked, "siteSettings" :document.getElementById("edp_siteSettings").checked, "offlineApps" :document.getElementById("edp_offlineApps").checked, } }; function callback () { window.close(); }; this.sendPreferences(preferences, callback); return false; }, /** * Updates the settings on the FireFound server. * * @param dict preferences The preferences to send to the server. * @param function callback The callback after the request completes. */ sendPreferences : function (preferences, callback) { // Both of these were set when the dialog loaded. var username = this.account; var password = this.getPassword(username); var json = { username : username, password : password, preferences : preferences }; json = JSON.stringify(json); var req = new XMLHttpRequest(); req.parent = this; req.open("POST", this.prefs.getCharPref("host") + "/api/preferences.json", true); req.setRequestHeader("Content-Type", "application/json"); req.setRequestHeader("Content-Length", json.length); req.onreadystatechange = function () { if (req.readyState == 4) { if (callback) { callback(); } } }; req.send(json); }, /** * Saves the chosen username/password pair to the login manager. * * @param string The chosen username. * @param string The chosen password. */ setPassword : function (username, password) { this.password = password; // Everything done with the username is lower-cased. username = username.toLowerCase(); // TODO Centralize this code. var loginManager = Components.classes["@mozilla.org/login-manager;1"].getService(Components.interfaces.nsILoginManager); var url = this.prefs.getCharPref("host") + "/"; var logins = loginManager.findLogins({}, url, "chrome://firefound", null); for (var j = 0; j < logins.length; j++) { loginManager.removeLogin(logins[j]); } var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo, "init"); var loginInfo = new nsLoginInfo(url, 'chrome://firefound', null, username, password, "", ""); loginManager.addLogin(loginInfo); }, /** * Retrieve the FireFound password for the given username. * * @param string The username. * @return string/bool The password, or false on failure. */ getPassword : function (username) { if (this.password) { return this.password; } var loginManager = Components.classes["@mozilla.org/login-manager;1"].getService(Components.interfaces.nsILoginManager); var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Components.interfaces.nsILoginInfo, "init"); var hostname = this.prefs.getCharPref("host") + "/"; var formSubmitURL = "chrome://firefound"; var logins = loginManager.findLogins({}, hostname, formSubmitURL, null); for (var i = 0; i < logins.length; i++) { this.password = logins[i].password; return logins[i].password; } return false; }, /** * The new location callback for the geolocation service. * * @param hash The new location data. */ newLocation : function (location) { var username = this.account; if (!this.account) { return; } var password = this.getPassword(username); if (!password) { this.getAccount(); return; } // Ping it out to the FireFound server. var json = { "username" : username, "password" : password, "location" : location }; json.location = FIREFOUND_CRYPT.encrypt(JSON.stringify(location), password); json.encrypted = true; json = JSON.stringify(json); var req = new XMLHttpRequest(); req.parent = this; req.open("POST", this.prefs.getCharPref("host") + "/api/location.json", true); req.setRequestHeader("Content-Type", "application/json"); req.setRequestHeader("Content-Length", json.length); req.onreadystatechange = function () { if (req.readyState == 4) { if (FIREFOUND.prefs.getBoolPref("debug")) { FIREFOUND.log("newLocation: " + req.responseText); } if (req.status == 200) { if (req.responseText) { // The only thing right now that would be in the response is a killswitch activation. var rv = JSON.parse(req.responseText); if (rv.killswitch) { // Get a password. req.parent.threaten(rv.killswitch_fields); } } } else { // Some error occurred. var rv = JSON.parse(req.responseText); if ("code" in rv && rv.code == "ERROR_NO_ACCOUNT") { // Disable the add-on. The user has either closed their account, // or they haven't been active in 60 days. Components.classes["@mozilla.org/extensions/manager;1"] .getService(Components.interfaces.nsIExtensionManager) .disableItem("firefound@efinke.com"); FIREFOUND.prefs.setCharPref("username", ""); FIREFOUND.getActiveFireFound().unload(); } } } }; req.send(json); }, /** * Threaten to clear all data if the password is not entered. * * @param array[string] fields The types of data that will be cleared on failure. */ threaten : function (fields) { var re = []; // The code in threat.xul takes care of closing after 30 seconds. window.openDialog("chrome://firefound/content/threat.xul", "threat", "chrome,modal,centerscreen,resizable=no", re); // Confirm that the password is correct. var username = re[0].toLowerCase(); var password = re[1]; if (username != this.account || password != this.getPassword(username)) { this.abortAbort(fields); } else { // Ping the FireFound server and clear the killswitch. var json = { "username" : username, "password" : password, "preferences" : { "killswitch": false } }; json = JSON.stringify(json); var req = new XMLHttpRequest(); req.parent = this; req.open("POST", this.prefs.getCharPref("host") + "/api/preferences.json", true); req.setRequestHeader("Content-Type", "application/json"); req.setRequestHeader("Content-Length", json.length); req.onreadystatechange = function () { if (req.readyState == 4) { if (FIREFOUND.prefs.getBoolPref("debug")) { req.parent.log("Threaten: " + req.responseText); } } }; req.send(json); } }, /** * Clear out all private data. * * @param array[string] fields The types of data that will be cleared. */ abortAbort : function (fields) { // Save the username/password so that FireFound can continue to act as a locater beacon. var username = this.account; var password = this.getPassword(username); var json = { "username" : username, "password" : password, "activated": true }; json = JSON.stringify(json); var san = new Sanitizer(); for (var i = 0; i < fields.length; i++) { var field = fields[i]; san.clearItem(field); if (field == "passwords") { // Reset the FireFound username/password so we can continue to track location. this.setPassword(username, password); } } var req = new XMLHttpRequest(); req.parent = this; req.open("POST", this.prefs.getCharPref("host") + "/api/killswitch.json", true); req.setRequestHeader("Content-Type", "application/json"); req.setRequestHeader("Content-Length", json.length); req.onreadystatechange = function () { if (req.readyState == 4) { // Shut down. var appStartup = Components.classes['@mozilla.org/toolkit/app-startup;1'].getService(Components.interfaces.nsIAppStartup); appStartup.quit(Components.interfaces.nsIAppStartup.eForceQuit); } }; req.send(json); }, /** * Retrieves and saves a KML file of the user's locations. */ downloadLocations : function () { if (document.getElementById("loading")) { // Show the "Retrieving data" loader. document.getElementById("loading").selectedIndex = 2; document.getElementById("loading").style.visibility = 'visible'; } // Save the username/password so that FireFound can continue to act as a locater beacon. var username = this.account; var password = this.getPassword(username); var json = { "username" : username, "password" : password, }; json = JSON.stringify(json); var req = new XMLHttpRequest(); req.parent = this; req.open("POST", this.prefs.getCharPref("host") + "/api/download.json", true); req.setRequestHeader("Content-Type", "application/json"); req.setRequestHeader("Content-Length", json.length); req.onreadystatechange = function () { if (req.readyState == 4) { // Prompt the user to save it. var data = req.responseText; var nsIFilePicker = Components.interfaces.nsIFilePicker; var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); fp.init(window, req.parent.strings.getString("firefound.download.title"), nsIFilePicker.modeSave); fp.appendFilter(req.parent.strings.getString("firefound.download.kml"), "*.kml"); fp.appendFilter(req.parent.strings.getString("firefound.download.all"), "*"); fp.defaultString = req.parent.strings.getString("firefound.download.filename") + ".kml"; var result = fp.show(); if (result != nsIFilePicker.returnCancel){ var file = fp.file; var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].getService(Components.interfaces.nsIScriptableUnicodeConverter); converter.charset = 'UTF-8'; data = converter.ConvertFromUnicode(data); var outputStream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream); outputStream.init(file, 0x04 | 0x08 | 0x20, 420, 0 ); outputStream.write(data, data.length); outputStream.close(); } if (document.getElementById("loading")) { document.getElementById("loading").style.visibility = 'hidden'; } } }; req.send(json); }, /** * Error callback for geolocation service. * * @param string The error string. */ locationError : function (error) { if (FIREFOUND.prefs.getBoolPref("debug")) { this.log("locationError: " + error); } }, /** * Log a message to the Error Console. * * @param string message */ log : function (message) { var consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService); consoleService.logStringMessage("FIREFOUND: " + message); } };